using Clancy.Common;
using Clancy.Data;
using Clancy.Interfaces;
using ReactiveUI;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Brushes = System.Windows.Media.Brushes;
using Rectangle = System.Windows.Shapes.Rectangle;

namespace Clancy.Ui.ViewModels
{
    public class ZoneEditorViewModel : ClancyEditorViewModelBase
    {
        private const int c_GrabPointRadius = 4;
        private const double c_MinimumSize = 2;
        private const double c_ZoneStrokeThickness = 2;
        private readonly BehaviorSubject<int> m_BoxSizeSubject = new BehaviorSubject<int>(50);
        private readonly BehaviorSubject<MotionZone> m_SelectedZoneObservable = new BehaviorSubject<MotionZone>(null);
        private readonly SolidColorBrush m_GrabPointFillBrush = new SolidColorBrush(Color.FromArgb(0xA0, 0xFF, 0x11, 0xAA));
        private readonly List<Ellipse> m_GrabPoints = new List<Ellipse>();
        private readonly SolidColorBrush m_GrabPointStrokeBrush = Brushes.HotPink;
        private readonly IMotionZoneFactory m_MotionZoneFactory;
        private readonly ObservableCollection<MotionZonePoint> m_PolygonPoints = new ObservableCollection<MotionZonePoint>();
        private readonly ISelectionManager m_SelectionManager;
        private readonly Brush m_ZoneFillBrush = new SolidColorBrush(Color.FromArgb(0xA0, 0x99, 0x99, 0xFF));
        private readonly Brush m_SelectedZoneFillBrush = new SolidColorBrush(Color.FromArgb(0xA0, 0xFF, 0x99, 0x99));
        private readonly ObservableCollection<MotionZone> m_Zones = new ObservableCollection<MotionZone>();
        private readonly Brush m_ZoneStrokeBrush = new SolidColorBrush(Color.FromArgb(0xFF, 0x33, 0x33, 0xFF));
        private ObservableAsPropertyHelper<int> m_BoxSizePropertyHelper;
        private ObservableAsPropertyHelper<ISurveilanceResultFrame> m_CurrentFramePropertyHelper;
        private Canvas m_DrawingCanvasPolygon;
        private Canvas m_DrawingCanvasZones;
        private Point? m_GrabpointDragStart;
        private ObservableAsPropertyHelper<ISelectable> m_SelectionPropertyHelper;
        private ISurveilanceStatus m_Surveilance;
        private ZoneEditMode m_ZoneEditMode;
        private Point? m_ZoneGrabPoint;
        private Polygon m_ZonePolygon;
        private ObservableAsPropertyHelper<MotionZone> m_SelectedZonePropertyHelper;

        public ZoneEditorViewModel(IOperationalAdapterProvider operationalAdapterProvider, ISurveilanceStorage surveilanceStorage, ISelectionManager selectionManager, IMotionZoneFactory motionZoneFactory)
            : base(operationalAdapterProvider, surveilanceStorage)
        {
            m_SelectionManager = selectionManager;

            AttachToCollection(m_PolygonPoints, UpdateFromGrabPoints, UpdateFromGrabPoints);

            m_MotionZoneFactory = motionZoneFactory;

            InitPropertyHelpers();

            InitCommands();
        }

        private enum ZoneEditMode
        {
            None,
            ModifyTop,
            ModifyBottom,
            ModifyLeft,
            ModifyRight,
        }

        public int BoxSize
        {
            get { return m_BoxSizePropertyHelper.Value; }
            set { m_BoxSizeSubject.OnNext(value); }
        }

        public ReactiveCommand<object> CmdClearAll { get; private set; }

        public ReactiveCommand<object> CmdSave { get; private set; }

        public ReactiveCommand<object> CmdDeleteZone { get; private set; }

        public BitmapSource CurrentFrame
        {
            get
            {
                if ((m_CurrentFramePropertyHelper == null) || (m_CurrentFramePropertyHelper.Value == null))
                {
                    return null;
                }
                return m_CurrentFramePropertyHelper.Value.Frame.ToBitmapSource();
            }
        }

        public ISurveilanceObject SelectedSurveilanceObject
        {
            get { return m_SelectionPropertyHelper.Value as ISurveilanceObject; }
        }

        private IEnumerable<Ellipse> GrabPoints
        {
            get
            {
                return m_PolygonPoints.Select(CreateGrabPoint).ToList();
            }
        }

        public void CreateNewZonePoint(MotionZonePoint point)
        {
            m_PolygonPoints.Add(point);
            m_GrabPoints.Add(CreateGrabPoint(point));
            Render();
        }

        public void Init()
        {
            InitSurveilanceObject(SelectedSurveilanceObject);

            Render();
        }

        public void SetDrawingCanvasPolygon(Canvas drawingCanvas)
        {
            m_DrawingCanvasPolygon = drawingCanvas;
        }

        public void SetDrawingCanvasZones(Canvas drawingCanvasZones)
        {
            m_DrawingCanvasZones = drawingCanvasZones;
        }

        protected override void DisposeElements()
        {
            if (m_CurrentFramePropertyHelper != null)
            {
                m_CurrentFramePropertyHelper.Dispose();
            }
            base.DisposeElements();
        }

        private void AddZone(IList items)
        {
            if (items == null)
                return;

            foreach (MotionZone zone in items.Cast<MotionZone>())
            {
                Rectangle zoneRect = CreateZoneRectangle(zone);
                m_DrawingCanvasZones.Children.Add(zoneRect);
            }
        }

        private void ClearAll()
        {
            m_GrabPoints.Clear();
            m_PolygonPoints.Clear();
            m_Zones.Clear();

            m_DrawingCanvasPolygon.Children.Clear();
            m_DrawingCanvasZones.Children.Clear();

            m_ZonePolygon = new Polygon { Stroke = Brushes.Black };

            InitializePolygon();
        }

        private Ellipse CreateGrabPoint(MotionZonePoint point)
        {
            Ellipse grabPoint = point.ToGrabPoint(c_GrabPointRadius, m_GrabPointStrokeBrush, 2, m_GrabPointFillBrush);
            grabPoint.Tag = point;
            grabPoint.MouseEnter += (sender, args) => { grabPoint.Stroke = Brushes.Aquamarine; };
            grabPoint.MouseLeave += (sender, args) => { grabPoint.Stroke = Brushes.HotPink; };
            grabPoint.MouseDown += HandeGrabpointMouseDown;
            grabPoint.MouseUp += (sender, args) => HandeGrabpointMouseUp(sender);
            grabPoint.MouseMove += (sender, args) => HandleGrabPointMouseMove(args, sender);

            return grabPoint;
        }

        private Rectangle CreateZoneRectangle(MotionZone zone)
        {
            Rectangle zoneRect = zone.ToShapesRectangle();
            zoneRect.Tag = zone;
            zoneRect.Stroke = m_ZoneStrokeBrush;
            zoneRect.Fill = m_ZoneFillBrush;
            zoneRect.StrokeThickness = c_ZoneStrokeThickness;

            zoneRect.MouseEnter += (sender, args) => { zoneRect.Stroke = Brushes.DarkBlue; };
            zoneRect.MouseLeave += (sender, args) => { zoneRect.Stroke = m_ZoneStrokeBrush; };
            zoneRect.MouseDown += HandeZoneMouseDown;
            zoneRect.MouseUp += HandeZoneMouseUp;
            zoneRect.MouseMove += HandleZoneMouseMove;

            return zoneRect;
        }

        private void HandeGrabpointMouseDown(object sender, MouseButtonEventArgs args)
        {
            Ellipse element = (Ellipse)sender;
            if (element != null)
            {
                m_GrabpointDragStart = args.GetPosition(element);
                element.CaptureMouse();
            }
        }

        private void HandeGrabpointMouseUp(object sender)
        {
            Ellipse element = (Ellipse)sender;
            if (element != null)
            {
                m_GrabpointDragStart = null;
                element.ReleaseMouseCapture();
            }
        }

        private void HandeZoneMouseDown(object sender, MouseButtonEventArgs mouseEventArgs)
        {
            Rectangle rectangle = sender as Rectangle;
            if (rectangle == null)
                return;

            m_SelectedZoneObservable.OnNext(rectangle.Tag as MotionZone);

            foreach(Rectangle rect in m_DrawingCanvasZones.Children)
            {
                if (rect.Tag == SelectedMotionZone)
                    rect.Fill = m_SelectedZoneFillBrush;
                else
                    rect.Fill = m_ZoneFillBrush;
            }

            Point pos = mouseEventArgs.GetPosition(rectangle);
            m_ZoneGrabPoint = mouseEventArgs.GetPosition(m_DrawingCanvasZones);

            if (pos.Y < 5)
            {
                m_ZoneEditMode = ZoneEditMode.ModifyTop;
            }
            else if (pos.Y > rectangle.Height - 5)
            {
                m_ZoneEditMode = ZoneEditMode.ModifyBottom;
            }
            else if (pos.X < 5)
            {
                m_ZoneEditMode = ZoneEditMode.ModifyLeft;
            }
            else if (pos.X > rectangle.Width - 5)
            {
                m_ZoneEditMode = ZoneEditMode.ModifyRight;
            }
            else
            {
                m_ZoneEditMode = ZoneEditMode.None;
            }

            rectangle.CaptureMouse();
        }

        private void HandeZoneMouseUp(object sender, MouseButtonEventArgs mouseButtonEventArgs)
        {
            m_ZoneGrabPoint = null;
            m_ZoneEditMode = ZoneEditMode.None;

            UIElement element = sender as UIElement;
            if (element != null)
            {
                element.ReleaseMouseCapture();
            }
        }

        private void HandleGrabPointMouseMove(MouseEventArgs args, object sender)
        {
            if (m_GrabpointDragStart != null && args.LeftButton == MouseButtonState.Pressed)
            {
                Ellipse element = (Ellipse)sender;
                if (element != null)
                {
                    Point p2 = args.GetPosition(m_DrawingCanvasPolygon);

                    double newLeft = p2.X - m_GrabpointDragStart.Value.X;
                    double newTop = p2.Y - m_GrabpointDragStart.Value.Y;

                    Canvas.SetLeft(element, newLeft);
                    Canvas.SetTop(element, newTop);

                    MotionZonePoint motionZonePoint = element.Tag as MotionZonePoint;
                    if (motionZonePoint != null)
                    {
                        motionZonePoint.UpdatePosition(newLeft + c_GrabPointRadius + 1,
                            newTop + c_GrabPointRadius + 1);
                    }

                    Render();
                }
            }
        }

        private void HandleZoneMouseMove(object sender, MouseEventArgs mouseEventArgs)
        {
            Rectangle rectangle = sender as Rectangle;
            if (rectangle == null)
                return;

            Point posWithinRectangle = mouseEventArgs.GetPosition(rectangle);
            Point posWithinCanvas = mouseEventArgs.GetPosition(m_DrawingCanvasZones);

            SetMouseCursorForPosition(posWithinRectangle, rectangle);

            if (!m_ZoneGrabPoint.HasValue)
            {
                return;
            }

            ModifyZoneShapre(rectangle, posWithinCanvas);
        }

        private void InitCommands()
        {
            CmdClearAll = ReactiveCommand.Create();
            CmdClearAll.Subscribe(_ => ClearAll());
            DisposableRxElements.Add(CmdClearAll);

            CmdSave = ReactiveCommand.Create(m_SelectionManager.CurrentSelection.Select(sel => (sel as ISurveilanceObject) != null));
            CmdSave.Subscribe(_ =>
            {
                SelectedSurveilanceObject.MotionZones = m_Zones.Select(zone => zone.ToDrawingRectangle()).ToArray();
            });
            DisposableRxElements.Add(CmdSave);

            CmdDeleteZone = ReactiveCommand.Create(m_SelectedZoneObservable.Select(zone => zone != null));
            CmdDeleteZone.Subscribe(_ => { DeleteSelectedZone(); });
            DisposableRxElements.Add(CmdDeleteZone);
        }

        private void DeleteSelectedZone()
        {
            if (SelectedMotionZone == null)
                return;

            if (!m_Zones.Contains(SelectedMotionZone))
                return;

            m_Zones.Remove(SelectedMotionZone);
            m_SelectedZoneObservable.OnNext(null);
        }

        private void InitializePolygon()
        {
            RefreshZonePolygon();
            foreach (var grabPoint in GrabPoints)
            {
                InsertElementToDrawingCanvas(grabPoint);
            }
        }

        private void InitPropertyHelpers()
        {
            m_SelectionPropertyHelper = new ObservableAsPropertyHelper<ISelectable>(m_SelectionManager.CurrentSelection,
                selection => this.RaisePropertyChanged("SelectedSurveilanceObject"));
            DisposableRxElements.Add(m_SelectionPropertyHelper);

            m_BoxSizePropertyHelper = new ObservableAsPropertyHelper<int>(m_BoxSizeSubject,
                _ =>
                {
                    this.RaisePropertyChanged("BoxSize");
                    Render();
                });
            DisposableRxElements.Add(m_BoxSizePropertyHelper);

            m_SelectedZonePropertyHelper = new ObservableAsPropertyHelper<MotionZone>(m_SelectedZoneObservable, _ =>
                {
                    this.RaisePropertyChanged("SelectedMotionZone");
                });

            AttachToCollection(m_Zones,
                items => AddZone(items as IList),
                items => RemoveZone(items as IList));
        }

        private MotionZone SelectedMotionZone
        {
            get
            {
                if (m_SelectedZonePropertyHelper == null)
                    return null;
                return m_SelectedZonePropertyHelper.Value;
            }
        }

        private void InitSurveilanceObject(ISurveilanceObject surveilanceObject)
        {
            if (surveilanceObject == null)
                return;

            OperationAdapter.StartVideoStream(SelectedSurveilanceObject);
            m_Surveilance = OperationAdapter.GetSurveilance(SelectedSurveilanceObject);

            m_CurrentFramePropertyHelper = new ObservableAsPropertyHelper<ISurveilanceResultFrame>(m_Surveilance.CurrentProcessedFrame, _ =>
            {
                this.RaisePropertyChanged("CurrentFrame");
            });

            ClearAll();

            if (SelectedSurveilanceObject.MotionZones != null)
            {
                m_Zones.AddRange(SelectedSurveilanceObject.MotionZones.Select(zone => zone.ToMotionZone()));
            }

            InsertElementToDrawingCanvas(m_ZonePolygon);

            InitializePolygon();

            this.RaisePropertyChanged("CurrentFrame");
        }

        private void InsertElementToDrawingCanvas(UIElement elementToInsert)
        {
            if (m_DrawingCanvasPolygon.Children.Contains(elementToInsert))
                return;
            m_DrawingCanvasPolygon.Children.Add(elementToInsert);
        }

        private void ModifyZoneShapre(Rectangle rectangle, Point posWithinCanvas)
        {
            Point anchorPoint = rectangle.GetAnchorPoint();
            switch (m_ZoneEditMode)
            {
                case ZoneEditMode.ModifyTop:
                    {
                        double delta = anchorPoint.Y - posWithinCanvas.Y;
                        double newHeight = rectangle.Height + delta;

                        if (newHeight >= c_MinimumSize)
                        {
                            Canvas.SetTop(rectangle, posWithinCanvas.Y);
                            rectangle.Height = newHeight;
                        }
                        break;
                    }
                case ZoneEditMode.ModifyBottom:
                    {
                        double newHeight = (posWithinCanvas.Y - anchorPoint.Y);
                        if (newHeight >= c_MinimumSize)
                        {
                            rectangle.Height = newHeight;
                        }
                        break;
                    }
                case ZoneEditMode.ModifyLeft:
                    {
                        double deltaX = anchorPoint.X - posWithinCanvas.X;
                        double newWidth = (rectangle.Width + deltaX);

                        if (newWidth >= c_MinimumSize)
                        {
                            Canvas.SetLeft(rectangle, posWithinCanvas.X);
                            rectangle.Width = newWidth;
                        }
                        break;
                    }
                case ZoneEditMode.ModifyRight:
                    {
                        double newWidth = (posWithinCanvas.X - anchorPoint.X);
                        if (newWidth >= c_MinimumSize)
                        {
                            rectangle.Width = newWidth;
                        }
                        break;
                    }
            }

            MotionZone zone = rectangle.Tag as MotionZone;
            if (zone == null)
                return;

            zone.TakeValuesFromRectanlge(rectangle);
        }

        private void RefreshZonePolygon()
        {
            m_ZonePolygon.Points.Clear();
            foreach (MotionZonePoint point in m_PolygonPoints.SortClockwise())
            {
                if (m_ZonePolygon.Points.Any(pt => point.IsSamePoint(pt)))
                    continue;
                m_ZonePolygon.Points.Add(point.ToSystemWindowsPoint());
            }
        }

        private void RemoveAllZoneRectanglesFromDrawingCanvas()
        {
            IEnumerable<Rectangle> exisitingZoneRectangles = m_DrawingCanvasZones.Children.Cast<UIElement>().Where(child => child is Rectangle).Cast<Rectangle>();
            foreach (Rectangle rect in exisitingZoneRectangles.ToArray())
            {
                m_DrawingCanvasZones.Children.Remove(rect);
            }
        }

        private void RemoveZone(IList list)
        {
            if (list == null)
                return;

            foreach (MotionZone zone in list.Cast<MotionZone>())
            {
                Rectangle rectangle = m_DrawingCanvasZones.Children.Cast<Rectangle>().FirstOrDefault(rect => rect.Tag == zone);
                if (rectangle == null)
                    continue;

                m_DrawingCanvasZones.Children.Remove(rectangle);
            }
        }

        private void Render()
        {
            if ((m_DrawingCanvasPolygon == null) || (m_DrawingCanvasZones == null))
                return;

            RefreshZonePolygon();

            SetMotionZonesFromPolygon();

            RenderZones();
        }

        private void RenderZones()
        {
            if (SelectedSurveilanceObject == null)
                return;

            RemoveAllZoneRectanglesFromDrawingCanvas();
        }

        private void SetMotionZonesFromPolygon()
        {
            if (m_ZonePolygon.Points.Count == 0)
            {
                return;
            }

            m_Zones.Clear();

            foreach (System.Drawing.Rectangle zone in m_MotionZoneFactory.CalculateZones(m_ZonePolygon, BoxSize))
            {
                m_Zones.Add(zone.ToMotionZone());
            }
        }

        private void SetMouseCursorForPosition(Point posWithinRectangle, Rectangle rectangle)
        {
            if (posWithinRectangle.Y < 5)
            {
                Mouse.SetCursor(Cursors.ScrollNS);
            }
            else if (posWithinRectangle.Y > rectangle.Height - 5)
            {
                Mouse.SetCursor(Cursors.ScrollNS);
            }
            else if (posWithinRectangle.X < 5)
            {
                Mouse.SetCursor(Cursors.ScrollWE);
            }
            else if (posWithinRectangle.X > rectangle.Width - 5)
            {
                Mouse.SetCursor(Cursors.ScrollWE);
            }
            else if (m_ZoneEditMode == ZoneEditMode.None)
            {
                Mouse.SetCursor(Cursors.Arrow);
            }
        }

        private void UpdateFromGrabPoints(object obj)
        {
            InitializePolygon();
        }
    }
}